#include "common.h"
#include "server.h"
#include "sock.h"
#include "http.h"
#include "cache_file.h"
#include "cache_dir.h"
#include "cache_index.h"

enum COMMAND {
	NONE = -1,
	SHUTDOWN,
	RELOAD,
};

typedef void SGHANDLE(int);
static void parse_options(int argc, char **argv);
static void usage(void) ;
static void conn_accept_handle(int fd, short flag, void *arg);
static void signal_catch(int sig, SGHANDLE *func, int flags);
static int max_open_fds_set(int num);
static void write_pid(void);
static int check_running_pid(void);
static void signal_routine(int sig);
static void signal_init(void);
static void child_monitor(char **argv);
static int command_string_to_type(const char *str);
static void server_reload(int sig);
static void server_listen_start(void);
static void server_listen_stop(void);

static conn_t *srv_con = NULL;
static int listen_fd = -1;
static const char *declaration = "Copyright (C) 2007-2008 Dolphin, Inc. \n" \
"License GPLv2: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.\n" \
"This is free software: you are free to change and redistribute it. There is  \n"\
"NO WARRANTY, to the extent permitted by law.";

static void server_listen_start(void)
{
	struct sockaddr_in srv_addr;

	/* init listen fd */
	master_base = event_base_new();
	assert(master_base);
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_port = htons(conf->server.port);
	srv_addr.sin_addr = conf->server.bind_addr4;
	listen_fd = sock_open(1, 1);
	sock_bind(listen_fd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
	sock_listen(listen_fd, 1024);
	srv_con = conn_new(listen_fd, SOCK_FLAG_READ, conn_accept_handle, \
					 NULL, NULL, master_base, &threads[0]);
	max_open_fds_set(conf->server.maxfds);
	debug_log(1, "start %d listen on %d\n", listen_fd, conf->server.port);
	
	/* setup thread */
	thread_init(conf->server.worker);
}

static void server_listen_stop(void)
{
	debug_log(1, "stop %d listen on %d\n", listen_fd, conf->server.port);
	conn_free(srv_con);
	srv_con = NULL;
	sock_close(listen_fd);
	listen_fd = -1;
	thread_clean();
}

static void server_reload(int sig)
{
	debug_log(1, "begin to reload when receive %d\n", sig);
	/* let's server restart */
	unlink("/var/run/dolphin.pid");
	exit(1);
}

static int command_string_to_type(const char *str)
{
	if(!str)
		return NONE;
	if(!strcasecmp(str, "shutdown")) 
		return SHUTDOWN;
	else if(!strcasecmp(str, "reload"))
		return RELOAD;
	else 
		return NONE;
}

static void signal_init(void)
{
	int normal_sigs[] = NORMAL_SIGNALS;
	int i;

	debug_log(5, "catch %d normal signals\n", ARRAY_SIZE(normal_sigs));
	/* catch normal signals */
	for ( i = 0; i < ARRAY_SIZE(normal_sigs); i++) 
		signal_catch(normal_sigs[i], signal_routine, SA_NODEFER|SA_RESTART);
	/* unblock all the signals, because if the current process is
	 * 	 * spawned in the previous signal handler, all the signals are
	 * 	 	 * blocked. In order to make it sense of signals, we should
	 * 	 	 	 * unblock them. Certainly, you should call this function as
	 * 	 	 	 	 * early as possible. :) */
	//pthread_sigmask(SIG_UNBLOCK, &act.sa_mask, NULL);
}

int main(int argc, char **argv)
{
	int pid;

	conf_default();
	parse_options(argc, argv);
	conf_parse();
	
	/* check pid */
	pid = check_running_pid();
	if(pid > 0) {
		fprintf(stderr, "dolphin %d is running\n", pid);
		if(shutdown_flag > 0) {
			fprintf(stderr, "Ready to kill dolphin(%d)\n", pid);
			kill(pid, SIGTERM);
			unlink("/var/run/dolphin.pid");
		}
		else if(reload_flag > 0) {
			fprintf(stderr, "reload %d\n", pid);
			kill(pid, SIGHUP);
		}
		return 0;
	}
	else if(shutdown_flag > 0) {
		printf("You want to kill dolphin.\n");
		return 0;
	}
	else if(reload_flag > 0) {
		printf("You want to reload dolphin.\n");
		return 0;
	}
	/* server init */
	log_init();
	/* monitor child. If child process is dump, parent will restart child again */
	if(conf->server.daemon)
		child_monitor(argv);
	
	/* signal handle */
	signal_init();
#if TEST_RESTART
	char *a = NULL;
	strcpy(a, "hello");
#endif
	/* init information of cache store */
	cache_file_table_init();
	cache_dir_init();
	cache_index_init();
	server_listen_start();
	/* success startup. write pid now */
	write_pid();
	
	event_base_run(master_base);

	return 0;  /* not reached */
}

static void parse_options(int argc, char **argv)
{
	assert(conf);
	extern char *optarg;
	int c;
	
	while(-1 != (c = getopt(argc, argv, "hvD:f:k:n?"))) {
		switch(c) {
			case 'f':
				if(!optarg) 
					usage();
				if(conf->log.conf) {
					mempool_free(conf->log.conf);
					conf->log.conf = mempool_dup(optarg, strlen(optarg));
				}
				else {
					conf->log.conf = mempool_dup(optarg, strlen(optarg));
				}
				conf->log.conf[strlen(optarg)] = '\0';
				break;
			case 'h':
				usage();
				break;
			case 'D':
				conf->server.debug_mode = 1;
				if(optarg) 
					conf->server.debug_level = atoi(optarg);
				break;
			case 'v': 
				fprintf(stderr, "%s\n%s\n", PACKAGE_STRING, declaration);
				exit(1);
				break;
			case 'k':
				if(optarg) {
					int res = command_string_to_type(optarg);
					if(res == SHUTDOWN)
						shutdown_flag = 1;
					else if(res == RELOAD)
						reload_flag = 1;
				}
				break;
			case 'n':
				conf->server.daemon = 0;
				break;
			default:
				break;
		}
	}
}

static void usage(void) 
{
	fprintf(stderr, "Usage: aladin [options]\n" \
					"Options: \n" \
					"       -D level                   Debug mode.\n" \
					"       -f config                  Setting config file.\n"\
					"       -k command                 Send signal to server.\n" \
					"                                  Now commands include 'shutdown', 'reload'.\n"\
					"       -n                         No daemon mode.\n"\
					"       -h                         Help information.\n"\
					"       -v                         Print version.\n");
	exit(1);
}


static void conn_accept_handle(int fd, short flag, void *arg)
{
	struct sockaddr_in client_addr;
	int c_fd;
	thread_t *thread;
	accept_t *at ;

	c_fd = sock_accept(fd, (struct sockaddr *)&client_addr, sizeof(client_addr));
	if(c_fd == -1)  {
		debug_log(3, "fd %d accept failed\n", fd);
		return;
	}
	thread = thread_select();
	at = mempool_alloc(sizeof(*at));
	if(!at)
		return;
	at->fd = c_fd;
	at->flag = SOCK_FLAG_READ;
	at->data = client_addr.sin_addr;
	at->handler = conn_request_read;
	INIT_LIST_HEAD(&at->list);
	if(-1 == queue_push(thread->waiting_conns, &at->list)) {
		debug_log(3, "failed to insert {fd %d} into queue\n", at->fd);
		return;
	}
	debug_log(3, "fd %d, flag %d, data %p, handler %p inserted\n", \
					at->fd, at->flag, at->data, at->handler);
	/* notify worker */
	if(write(thread->notify_send_fd, "", 1) == -1)
		error_log("notify %u failed\n", thread->id);
}


void server_down(int signum)
{
	debug_log(1, "after %d come, begin to close server.\n", signum);
	server_listen_stop();
	cache_dir_close();

	if( -1 == unlink("/var/run/dolphin.pid")) 
		debug_log(1, "failed to unlink %s\n", "/var/run/dolphin.pid");

	exit(0);
}


static void signal_catch(int sig, SGHANDLE *func, int flags)
{
#if HAVE_SIGACTION
	struct sigaction sa;
	sa.sa_handler = func;
	sa.sa_flags = flags;
	sigemptyset(&sa.sa_mask);
	if (sigaction(sig, &sa, NULL) < 0)
		debug_log(3, "sig=%d func=%p: %s failed\n", sig, func);
#else
	signal(sig, func);
#endif
}

static int max_open_fds_set(int num)
{
	struct rlimit limit;

	if(-1 == getrlimit(RLIMIT_NOFILE, &limit)) {
		/* give error message */
		error_log("failed to getrlimit %s\n", RLIMIT_NOFILE);
		return -1;
	}
	limit.rlim_cur = limit.rlim_max = num;
	if(-1 == setrlimit(RLIMIT_NOFILE, &limit)) {
		/* give error message */
		error_log("failed to setrlimit %d fds\n", num);
		return -1;
	}

	return 0;
}


static void write_pid(void)
{
		FILE *f = fopen("/var/run/dolphin.pid", "w");
		if(!f) {
			error_log("failed to open/create %s.\n", "/var/run/dolphin.pid");
			return;
		}
		running_pid = getpid();
		fprintf(f, "%d", running_pid);
		fclose(f);
		debug_log(5, "write pid %d.\n", getpid());
}

static int check_running_pid(void)
{
		int pid = -1;
		int fd;
		struct stat st;
		int res;
		char buf[16];

		if(stat("/var/run/dolphin.pid", &st) == -1) {
			debug_log(1, "failed to stat %s\n", "/var/run/dolphin.pid");
			return -1;
		}
		if(-1 == (fd = open("/var/run/dolphin.pid", O_RDONLY))) {
			error_log("%d failed to open\n", fd);
			return -1;
		}
		if(-1 == (res = read(fd, buf, 16))) {
			error_log(" %d faield to read pid file\n", fd);
			return -1;
		}
		buf[res] = '\0';
		pid = atoi(buf);
		close(fd);
		debug_log(5, "dolphin %d is running.\n", pid);
		return pid;
}

static void signal_routine(int sig)
{
	debug_log(5, "received %d signal\n", sig);
	switch(sig) {
		case SIGTERM:
			server_down(sig);
			break;
		case SIGHUP:
			server_reload(sig);
			break;
		default:
			break;
	}
}

static void child_monitor(char **argv)
{
	int failed = 0;
	int nullfd;
	int child_pid;
	int status;
	time_t start, stop;
	char *prog = NULL;
	int sleep_interval = 5;

	/* connect stdio to /dev/null */
	nullfd = open("/dev/null", O_RDWR);
	if (nullfd < 0) {
		error_log("failed to open %s\n", "/dev/null");
		exit(0);
	}
	dup2(nullfd, 0);
	dup2(nullfd, 1);
	dup2(nullfd, 2);

	if(argv[0][0] == '(') 
		return;
	/* go into daemon */
	if ((child_pid = fork()) < 0)
		error_log("fork failed: %s", argv[0]);
	else if (child_pid > 0)
		exit(0);
	if (setsid() < 0)
		error_log("setsid failed: %s", argv[0]);

	for(; ;) {
		if((child_pid = fork()) == 0) {
			/* child */
			prog = mempool_dup(argv[0], strlen(argv[0]));
			prog[strlen(argv[0])] = '\0';
			argv[0] = mempool_dup("(dolphin)", sizeof("(dolphin)"));
			argv[0][sizeof("(dolphin)")] = '\0';
			execvp(prog, argv);
		}
		/* parent */
		time(&start);
		signal_catch(SIGINT, SIG_IGN, SA_RESTART);
		child_pid = waitpid(-1, &status, 0);
		time(&stop);
		debug_log(1, "found child %d exit.\n", child_pid);
		if(stop - start < 10) 
			failed++;
		else 
			failed = 0;
		if(failed > 3) {
			error_log("failed to restart child %s, failed %d\n", argv[0], failed);
			exit(1);
		}
		if (WIFEXITED(status)) {
			if (WEXITSTATUS(status) == 0)
				exit(0);
			else 
				error_log("%d child stop abnormally.\n", status);
		}
			
		if (WIFSIGNALED(status)) {
			switch (WTERMSIG(status)) {
				case SIGKILL:
				case SIGTERM:
					exit(0);
					break;
				default:
					break;
			}
		}
		signal_catch(SIGINT, SIG_DFL, SA_RESTART);
		signal_catch(SIGTERM, SIG_DFL, SA_RESTART);
		debug_log(1, "wait %ds, then restart %s again ...\n", prog, sleep_interval);
		sleep(sleep_interval);
	}
}
